#include "BaseFactory.h"

DRAGONBONES_NAMESPACE_BEGIN

JSONDataParser BaseFactory::_jsonParser;
BinaryDataParser BaseFactory::_binaryParser;

TextureData* BaseFactory::_getTextureData(std::string_view textureAtlasName, std::string_view textureName) const
{
    const auto iterator = _textureAtlasDataMap.find(textureAtlasName);
    if (iterator != _textureAtlasDataMap.end())
    {
        for (const auto textureAtlasData : iterator->second)
        {
            const auto textureData = textureAtlasData->getTexture(textureName);
            if (textureData != nullptr)
            {
                return textureData;
            }
        }
    }

    if (autoSearch)
    {
        for (const auto& pair : _textureAtlasDataMap)
        {
            for (const auto textureAtlasData : pair.second)
            {
                if (textureAtlasData->autoSearch)
                {
                    const auto textureData = textureAtlasData->getTexture(textureName);
                    if (textureData != nullptr)
                    {
                        return textureData;
                    }
                }
            }
        }
    }

    return nullptr;
}

bool BaseFactory::_fillBuildArmaturePackage(BuildArmaturePackage& dataPackage,
                                            std::string_view dragonBonesName,
                                            std::string_view armatureName,
                                            std::string_view skinName,
                                            std::string_view textureAtlasName) const
{
    auto mapName                     = dragonBonesName;
    DragonBonesData* dragonBonesData = nullptr;
    ArmatureData* armatureData       = nullptr;

    if (!mapName.empty())
    {
        const auto iterator = _dragonBonesDataMap.find(mapName);
        if (iterator != _dragonBonesDataMap.end())
        {
            dragonBonesData = iterator->second;
            armatureData    = dragonBonesData->getArmature(armatureName);
        }
    }

    if (armatureData == nullptr && (mapName.empty() || autoSearch))
    {
        for (const auto& pair : _dragonBonesDataMap)
        {
            dragonBonesData = pair.second;
            if (mapName.empty() || dragonBonesData->autoSearch)
            {
                armatureData = dragonBonesData->getArmature(armatureName);
                if (armatureData != nullptr)
                {
                    mapName = pair.first;
                    break;
                }
            }
        }
    }

    if (armatureData != nullptr)
    {
        dataPackage.dataName         = mapName;
        dataPackage.textureAtlasName = textureAtlasName;
        dataPackage.data             = dragonBonesData;
        dataPackage.armature         = armatureData;
        dataPackage.skin             = nullptr;

        if (!skinName.empty())
        {
            dataPackage.skin = armatureData->getSkin(skinName);
            if (dataPackage.skin == nullptr && autoSearch)
            {
                for (const auto& pair : _dragonBonesDataMap)
                {
                    const auto skinDragonBonesData = pair.second;
                    const auto skinArmatureData    = skinDragonBonesData->getArmature(skinName);
                    if (skinArmatureData != nullptr)
                    {
                        dataPackage.skin = skinArmatureData->defaultSkin;
                        break;
                    }
                }
            }
        }

        if (dataPackage.skin == nullptr)
        {
            dataPackage.skin = armatureData->defaultSkin;
        }

        return true;
    }

    return false;
}

void BaseFactory::_buildBones(const BuildArmaturePackage& dataPackage, Armature* armature) const
{
    for (const auto boneData : dataPackage.armature->sortedBones)
    {
        const auto bone = BaseObject::borrowObject<Bone>();
        bone->init(boneData, armature);
    }

    for (const auto& pair : dataPackage.armature->constraints)
    {
        // TODO more constraint type.
        const auto constraint = BaseObject::borrowObject<IKConstraint>();
        constraint->init(pair.second, armature);
        armature->_addConstraint(constraint);
    }
}

void BaseFactory::_buildSlots(const BuildArmaturePackage& dataPackage, Armature* armature) const
{
    const auto currentSkin = dataPackage.skin;
    const auto defaultSkin = dataPackage.armature->defaultSkin;
    if (currentSkin == nullptr || defaultSkin == nullptr)
    {
        return;
    }

    hlookup::string_map<std::vector<DisplayData*>*> skinSlots;
    for (auto& pair : defaultSkin->displays)
    {
        auto& displays        = pair.second;
        skinSlots[pair.first] = &displays;
    }

    if (currentSkin != defaultSkin)
    {
        for (auto& pair : currentSkin->displays)
        {
            auto& displays        = pair.second;
            skinSlots[pair.first] = &displays;
        }
    }

    for (const auto slotData : dataPackage.armature->sortedSlots)
    {
        const auto displayDatas = skinSlots[slotData->name];
        const auto slot         = _buildSlot(dataPackage, slotData, armature);
        slot->setRawDisplayDatas(displayDatas);

        if (displayDatas != nullptr)
        {
            std::vector<std::pair<void*, DisplayType>> displayList;

            for (const auto displayData : *displayDatas)
            {
                if (displayData != nullptr)
                {
                    displayList.push_back(_getSlotDisplay(&dataPackage, displayData, nullptr, slot));
                }
                else
                {
                    displayList.push_back(std::make_pair(nullptr, DisplayType::Image));
                }
            }

            slot->_setDisplayList(displayList);
        }

        slot->_setDisplayIndex(slotData->displayIndex, true);
    }
}

Armature* BaseFactory::_buildChildArmature(const BuildArmaturePackage* dataPackage,
                                           Slot* slot,
                                           DisplayData* displayData) const
{
    return buildArmature(displayData->path, dataPackage != nullptr ? dataPackage->dataName : "", "",
                         dataPackage != nullptr ? dataPackage->textureAtlasName : "");
}

std::pair<void*, DisplayType> BaseFactory::_getSlotDisplay(const BuildArmaturePackage* dataPackage,
                                                           DisplayData* displayData,
                                                           DisplayData* rawDisplayData,
                                                           Slot* slot) const
{
    std::string dataName = "";

    if (dataPackage != nullptr)
    {
        dataName = dataPackage->dataName;
    }
    else
    {
        for (const auto& pair : _dragonBonesDataMap)
        {
            if (pair.second == displayData->parent->parent->parent)
            {
                dataName = pair.first;
            }
        }

        if (dataName.empty())
        {
            dataName = displayData->parent->parent->parent->name;
        }
    }

    std::pair<void*, DisplayType> display(nullptr, DisplayType::Image);
    switch (displayData->type)
    {
    case DisplayType::Image:
    {
        auto imageDisplayData = static_cast<ImageDisplayData*>(displayData);
        if (imageDisplayData->texture == nullptr)
        {
            imageDisplayData->texture = _getTextureData(dataName, displayData->path);
        }
        else if (dataPackage != nullptr && !dataPackage->textureAtlasName.empty())
        {
            imageDisplayData->texture = _getTextureData(dataPackage->textureAtlasName, displayData->path);
        }

        display.first  = slot->_rawDisplay;
        display.second = DisplayType::Image;
        break;
    }

    case DisplayType::Mesh:
    {
        auto meshDisplayData = static_cast<MeshDisplayData*>(displayData);
        if (meshDisplayData->texture == nullptr)
        {
            meshDisplayData->texture = _getTextureData(dataName, meshDisplayData->path);
        }
        else if (dataPackage != nullptr && !dataPackage->textureAtlasName.empty())
        {
            meshDisplayData->texture = _getTextureData(dataPackage->textureAtlasName, meshDisplayData->path);
        }

        if (_isSupportMesh())
        {
            display.first  = slot->_meshDisplay;
            display.second = DisplayType::Mesh;
        }
        else
        {
            display.first  = slot->_rawDisplay;
            display.second = DisplayType::Image;
        }
        break;
    }

    case DisplayType::Armature:
    {
        auto armatureDisplayData = static_cast<ArmatureDisplayData*>(displayData);
        const auto childArmature = _buildChildArmature(dataPackage, slot, displayData);
        if (childArmature != nullptr)
        {
            childArmature->inheritAnimation = armatureDisplayData->inheritAnimation;
            if (!childArmature->inheritAnimation)
            {
                const auto actions = !armatureDisplayData->actions.empty()
                                         ? &(armatureDisplayData->actions)
                                         : &(childArmature->_armatureData->defaultActions);
                if (!actions->empty())
                {
                    for (const auto action : *actions)
                    {
                        childArmature->getAnimation()->fadeIn(action->name);
                    }
                }
                else
                {
                    childArmature->getAnimation()->play();
                }
            }

            armatureDisplayData->armature = childArmature->_armatureData;  //
        }

        display.first  = childArmature;
        display.second = DisplayType::Armature;
        break;
    }

    case DisplayType::BoundingBox:
        break;

    default:
        break;
    }

    return display;
}

DragonBonesData* BaseFactory::parseDragonBonesData(const char* rawData, std::string_view name, float scale)
{
    DRAGONBONES_ASSERT(rawData != nullptr, "");

    DataParser* dataParser = nullptr;

    if (rawData[0] == 'D' && rawData[1] == 'B' && rawData[2] == 'D' && rawData[3] == 'T')
    {
        dataParser = &_binaryParser;
    }
    else
    {
        dataParser = _dataParser;
    }

    const auto dragonBonesData = dataParser->parseDragonBonesData(rawData, scale);

    while (true)
    {
        const auto textureAtlasData = _buildTextureAtlasData(nullptr, nullptr);
        if (dataParser->parseTextureAtlasData(nullptr, *textureAtlasData, scale))
        {
            addTextureAtlasData(textureAtlasData, name);
        }
        else
        {
            textureAtlasData->returnToPool();
            break;
        }
    }

    if (dragonBonesData != nullptr)
    {
        addDragonBonesData(dragonBonesData, name);
    }

    return dragonBonesData;
}

TextureAtlasData* BaseFactory::parseTextureAtlasData(const char* rawData,
                                                     void* textureAtlas,
                                                     std::string_view name,
                                                     float scale)
{
    const auto textureAtlasData = _buildTextureAtlasData(nullptr, nullptr);
    _dataParser->parseTextureAtlasData(rawData, *textureAtlasData, scale);
    _buildTextureAtlasData(textureAtlasData, textureAtlas);
    addTextureAtlasData(textureAtlasData, name);

    return textureAtlasData;
}

void BaseFactory::addDragonBonesData(DragonBonesData* data, std::string_view name)
{
    const auto& mapName = !name.empty() ? name : data->name;
    if (_dragonBonesDataMap.find(mapName) != _dragonBonesDataMap.cend())
    {
        if (_dragonBonesDataMap[name] == data)
        {
            return;
        }

        DRAGONBONES_ASSERT(false, "Can not add same name data: " + name);
        return;
    }

    _dragonBonesDataMap[mapName] = data;
}

void BaseFactory::removeDragonBonesData(std::string_view name, bool disposeData)
{
    const auto iterator = _dragonBonesDataMap.find(name);
    if (iterator != _dragonBonesDataMap.cend())
    {
        if (disposeData)
        {
            iterator->second->returnToPool();
        }

        _dragonBonesDataMap.erase(iterator);
    }
}

void BaseFactory::addTextureAtlasData(TextureAtlasData* data, std::string_view name)
{
    const auto& mapName    = !name.empty() ? name : data->name;
    auto& textureAtlasList = _textureAtlasDataMap[mapName];
    if (std::find(textureAtlasList.cbegin(), textureAtlasList.cend(), data) == textureAtlasList.cend())
    {
        textureAtlasList.push_back(data);
    }
}

void BaseFactory::removeTextureAtlasData(std::string_view name, bool disposeData)
{
    const auto iterator = _textureAtlasDataMap.find(name);
    if (iterator != _textureAtlasDataMap.end())
    {
        if (disposeData)
        {
            for (const auto textureAtlasData : iterator->second)
            {
                textureAtlasData->returnToPool();
            }
        }

        _textureAtlasDataMap.erase(iterator);
    }
}

ArmatureData* BaseFactory::getArmatureData(std::string_view name, std::string_view dragonBonesName) const
{
    BuildArmaturePackage dataPackage;
    if (!_fillBuildArmaturePackage(dataPackage, dragonBonesName, name, "", ""))
    {
        return nullptr;
    }

    return dataPackage.armature;
}

void BaseFactory::clear(bool disposeData)
{
    if (disposeData)
    {
        for (const auto& pair : _dragonBonesDataMap)
        {
            pair.second->returnToPool();
        }

        for (const auto& pair : _textureAtlasDataMap)
        {
            for (const auto textureAtlasData : pair.second)
            {
                textureAtlasData->returnToPool();
            }
        }
    }

    _dragonBonesDataMap.clear();
    _textureAtlasDataMap.clear();
}

Armature* BaseFactory::buildArmature(std::string_view armatureName,
                                     std::string_view dragonBonesName,
                                     std::string_view skinName,
                                     std::string_view textureAtlasName) const
{
    BuildArmaturePackage dataPackage;
    if (!_fillBuildArmaturePackage(dataPackage, dragonBonesName, armatureName, skinName, textureAtlasName))
    {
        DRAGONBONES_ASSERT(
            false, "No armature data: " + armatureName + ", " + (!dragonBonesName.empty() ? dragonBonesName : ""));
        return nullptr;
    }

    const auto armature = _buildArmature(dataPackage);
    _buildBones(dataPackage, armature);
    _buildSlots(dataPackage, armature);
    armature->invalidUpdate("", true);
    armature->advanceTime(0.0f);  // Update armature pose.

    return armature;
}

void BaseFactory::replaceDisplay(Slot* slot, DisplayData* displayData, int displayIndex) const
{
    if (displayIndex < 0)
    {
        displayIndex = slot->getDisplayIndex();
    }

    if (displayIndex < 0)
    {
        displayIndex = 0;
    }

    slot->replaceDisplayData(displayData, displayIndex);

    auto displayList = slot->getDisplayList();  // Copy.
    if (displayList.size() <= (unsigned)displayIndex)
    {
        displayList.resize(displayIndex + 1, std::make_pair(nullptr, DisplayType::Image));
    }

    if (displayData != nullptr)
    {
        const auto rawDisplayDatas = slot->getRawDisplayDatas();
        displayList[displayIndex] =
            _getSlotDisplay(nullptr, displayData,
                            rawDisplayDatas != nullptr && (unsigned)displayIndex < rawDisplayDatas->size()
                                ? rawDisplayDatas->at(displayIndex)
                                : nullptr,
                            slot);
    }
    else
    {
        displayList[displayIndex] = std::make_pair(nullptr, DisplayType::Image);
    }

    slot->setDisplayList(displayList);
}

bool BaseFactory::replaceSlotDisplay(std::string_view dragonBonesName,
                                     std::string_view armatureName,
                                     std::string_view slotName,
                                     std::string_view displayName,
                                     Slot* slot,
                                     int displayIndex) const
{
    DRAGONBONES_ASSERT(slot, "Arguments error.");

    const auto armatureData = getArmatureData(armatureName, dragonBonesName);
    if (!armatureData || !armatureData->defaultSkin)
    {
        return false;
    }

    const auto displayData = armatureData->defaultSkin->getDisplay(slotName, displayName);
    if (!displayData)
    {
        return false;
    }

    replaceDisplay(slot, displayData, displayIndex);

    return true;
}

bool BaseFactory::replaceSlotDisplayList(std::string_view dragonBonesName,
                                         std::string_view armatureName,
                                         std::string_view slotName,
                                         Slot* slot) const
{
    DRAGONBONES_ASSERT(slot, "Arguments error.");

    const auto armatureData = getArmatureData(armatureName, dragonBonesName);
    if (!armatureData || !armatureData->defaultSkin)
    {
        return false;
    }

    const auto displays = armatureData->defaultSkin->getDisplays(slotName);
    if (!displays)
    {
        return false;
    }

    auto displayIndex = 0;
    for (const auto displayData : *displays)
    {
        replaceDisplay(slot, displayData, displayIndex++);
    }

    return true;
}

bool BaseFactory::replaceSkin(Armature* armature,
                              SkinData* skin,
                              bool isOverride,
                              const std::vector<std::string>* exclude) const
{
    DRAGONBONES_ASSERT(armature && skin, "Arguments error.");

    auto success           = false;
    const auto defaultSkin = skin->parent->defaultSkin;

    for (const auto slot : armature->getSlots())
    {
        if (exclude != nullptr && std::find(exclude->cbegin(), exclude->cend(), slot->getName()) != exclude->cend())
        {
            continue;
        }

        auto displays = skin->getDisplays(slot->getName());
        if (displays == nullptr)
        {
            if (defaultSkin != nullptr && skin != defaultSkin)
            {
                displays = defaultSkin->getDisplays(slot->getName());
            }

            if (isOverride)
            {
                std::vector<std::pair<void*, DisplayType>> displayList;
                slot->setRawDisplayDatas(nullptr);
                slot->setDisplayList(displayList);
            }
            continue;
        }

        auto displayList = slot->getDisplayList();  // Copy.
        displayList.resize(displays->size(), std::make_pair(nullptr, DisplayType::Image));
        for (std::size_t i = 0, l = displays->size(); i < l; ++i)
        {
            const auto displayData = displays->at(i);
            if (displayData != nullptr)
            {
                displayList[i] = _getSlotDisplay(nullptr, displayData, nullptr, slot);
            }
            else
            {
                displayList[i] = std::make_pair(nullptr, DisplayType::Image);
            }
        }

        success = true;
        slot->setRawDisplayDatas(displays);
        slot->setDisplayList(displayList);
    }

    return success;
}

bool BaseFactory::replaceAnimation(Armature* armature, ArmatureData* armatureData, bool isReplaceAll) const
{
    const auto skinData = armatureData->defaultSkin;
    if (skinData == nullptr)
    {
        return false;
    }

    if (isReplaceAll)
    {
        armature->getAnimation()->setAnimations(armatureData->animations);
    }
    else
    {
        auto animations = armature->getAnimation()->getAnimations();  // Copy.
        for (const auto& pair : armatureData->animations)
        {
            animations[pair.first] = pair.second;
        }

        armature->getAnimation()->setAnimations(animations);
    }

    for (const auto slot : armature->getSlots())
    {
        unsigned index = 0;
        for (const auto& pair : slot->getDisplayList())
        {
            if (pair.second == DisplayType::Armature)
            {
                auto displayDatas = skinData->getDisplays(slot->getName());
                if (displayDatas != nullptr && index < displayDatas->size())
                {
                    const auto displayData = (*displayDatas)[index];
                    if (displayData != nullptr && displayData->type == DisplayType::Armature)
                    {
                        const auto childArmatureData =
                            getArmatureData(displayData->path, displayData->parent->parent->parent->name);
                        if (childArmatureData != nullptr)
                        {
                            replaceAnimation((Armature*)pair.first, childArmatureData, isReplaceAll);
                        }
                    }
                }
            }

            index++;
        }
    }

    return true;
}

DRAGONBONES_NAMESPACE_END
